﻿//===============================================================================
// Jammer.NET Development Library
// Jmr.Entity.Poco
//===============================================================================
// Copyright © Junaid Fakhruddin, Matthew Leedham and Randolph Cabral.  
// All rights reserved.
//===============================================================================
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================

using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;

namespace Jmr.Entity.Poco
{
	/// <summary>
	/// EntityProxy class.
	/// </summary>
	/// <typeparam name="TEntity">Type of entity to proxy.</typeparam>
	public class EntityProxy<TEntity>
	{
		/// <summary>
		/// EntityProxy constructor.
		/// </summary>
		/// <param name="session">Reference to the session instance.</param>
		/// <param name="entityargref">Reference to the entity argument instance.</param>
		public EntityProxy(IEntitySession session, object entityargref)
		{
			Session = session;
			EntityArgRef = entityargref;
			LoadedFromDb = false;

			if (!Session.EnableDeferredLoading)
			{
				LoadData();
				LoadedFromDb = true;
			}
		}

		/// <summary>
		/// Gets or sets a reference to the session instance.
		/// </summary>
		public IEntitySession Session { get; set; }

		/// <summary>
		/// Gets or sets a flag indicating if the current entity state is loaded from a database.
		/// </summary>
		public bool LoadedFromDb { get; set; }

		/// <summary>
		/// Gets or sets a reference to the entity instance.
		/// </summary>
		public TEntity EntityRef { get; set; }

		/// <summary>
		/// Gets or sets a reference to the entity argument instance.
		/// </summary>
		public object EntityArgRef { get; set; }

		/// <summary>
		/// Loads the entity properties with data from the database.
		/// </summary>
		public void LoadData()
		{
			EntityRef = Session.FetchSingle<TEntity>(EntityArgRef);
		}

		/// <summary>
		/// Implicit operator for the generic entity type.
		/// </summary>
		/// <param name="instance">Reference to the EntityProxy instance."/></param>
		/// <returns>Returns the actual entity reference.</returns>
		public static implicit operator TEntity(EntityProxy<TEntity> instance)
		{
			if (!instance.LoadedFromDb) { instance.LoadData(); }
			return instance.EntityRef;
		}
	}

	/// <summary>
	/// ListProxy class.
	/// </summary>
	/// <typeparam name="TEntity">Type of entity the list contains.</typeparam>
	public class ListProxy<TEntity> : IList<TEntity>, INotifyCollectionChanged
	{
		/// <summary>
		/// ListProxy constructor.
		/// </summary>
		/// <param name="session">Reference to the session instance.</param>
		/// <param name="parententity">Reference to the parent entity instance.</param>
		/// <param name="sprocname">Name of the stored procedure used to load the list.</param>
		public ListProxy(IEntitySession session, object parententity, string sprocname)
		{
			Session = session;
			EntityArgRef = parententity;
			SprocName = sprocname;
			LoadedFromDb = false;
			Innerlist = new List<TEntity>();

			if (!Session.EnableDeferredLoading)
				DoLoad();
		}

		/// <summary>
		/// Gets or sets a flag indicating if the current entity state is loaded from a database.
		/// </summary>
		public bool LoadedFromDb { get; set; }
		
		/// <summary>
		/// Gets or sets a reference to the session instance.
		/// </summary>
		public IEntitySession Session { get; set; }
		
		/// <summary>
		/// Gets or sets a reference to the entity argument instance.
		/// </summary>
		public object EntityArgRef { get; set; }

		/// <summary>
		/// Gets or sets a string indicating the stored procedure used to load the list.
		/// </summary>
		public string SprocName { get; set; }

		/// <summary>
		/// Gets or sets the inner list.
		/// </summary>
		public List<TEntity> Innerlist { get; set; }

		/// <summary>
		/// Gets the number of items in the list.
		/// </summary>
		public int Count
		{
			get
			{
				DoLoad();
				return Innerlist.Count();
			}
		}

		/// <summary>
		/// Loads the list from the database.
		/// </summary>
		public void DoLoad()
		{
			if (LoadedFromDb) { return; }
			var result = Session.ExecuteQuerySP<TEntity>(SprocName, EntityArgRef);
			AddRange(result);
			LoadedFromDb = true;
		}

		/// <summary>
		/// Adds the items from a given collection.
		/// </summary>
		/// <param name="collection">A collection of items to add to the list.</param>
		public void AddRange(IEnumerable<TEntity> collection)
		{
			Innerlist.AddRange(collection);
      
      if (CollectionChanged != null)
        CollectionChanged(this, 
          new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection));
		}

		#region IList<TEntity> Members

		/// <summary>
		/// Returns the index of the current item.
		/// </summary>
		/// <param name="item">The item to find in the list.</param>
		/// <returns>Returns the index position of the current item.</returns>
		public int IndexOf(TEntity item)
		{
			return Innerlist.IndexOf(item);
		}

		/// <summary>
		/// Inserts an item at the desired index position.
		/// </summary>
		/// <param name="index">The index position to add the item to.</param>
		/// <param name="item">The item to add to the list.</param>
		public void Insert(int index, TEntity item)
		{
			Session.PersistEntity(item);
			Innerlist.Insert(index, item);
      
      if (CollectionChanged != null)
        CollectionChanged(this,
          new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
    }

		/// <summary>
		/// Removes an item from the list at the given index position.
		/// </summary>
		/// <param name="index">The index position to remove.</param>
		public void RemoveAt(int index)
		{
      var entity = this[index];
			Session.AbandonEntity(entity);
			Innerlist.RemoveAt(index);
      
      if (CollectionChanged != null)
        CollectionChanged(this,
          new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, entity, index));
    }

		/// <summary>
		/// List Indexer.
		/// </summary>
		/// <param name="index">The index position to retrieve the item at.</param>
		/// <returns>Returns the item at the given position.</returns>
		public TEntity this[int index]
		{
			get
			{
				return Innerlist[index];
			}
			set
			{
				Innerlist[index] = value;
        
        if (CollectionChanged != null)
          CollectionChanged(this,
            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, index));
      }
		}

		#endregion

		#region ICollection<TEntity> Members

		/// <summary>
		/// Adds an item to the end of the list.
		/// </summary>
		/// <param name="item">Item to add.</param>
		public void Add(TEntity item)
		{
			Session.PersistEntity(item);
			Innerlist.Add(item);
      
      if (CollectionChanged != null)
        CollectionChanged(this,
          new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
    }

		/// <summary>
		/// Clears the contents of the list.
		/// </summary>
		public void Clear()
		{
			Innerlist.Clear();
      
      if (CollectionChanged != null)
        CollectionChanged(this,
          new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

		/// <summary>
		/// Determines whether an item is the list.
		/// </summary>
		/// <param name="item">The item to find in the list.</param>
		/// <returns>Returns whether the given item is the list.</returns>
		public bool Contains(TEntity item)
		{
			return Innerlist.Contains(item);
		}

		/// <summary>
		/// Copies the items of the list in to an array.
		/// </summary>
		/// <param name="array">Destination array.</param>
		/// <param name="arrayIndex">The index position of the array to start copying to.</param>
		public void CopyTo(TEntity[] array, int arrayIndex)
		{
			Innerlist.CopyTo(array, arrayIndex);
		}

		/// <summary>
		/// Indicates whether the list can be added to.
		/// </summary>
		public bool IsReadOnly
		{
			get { return false; }
		}

		/// <summary>
		/// Removes the matching item in the list.
		/// </summary>
		/// <param name="item">The item to remove.</param>
		/// <returns>Returns success as a boolean value.</returns>
		public bool Remove(TEntity item)
		{
			Session.AbandonEntity(item);

      if (CollectionChanged != null)
        CollectionChanged(this,
          new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
			
      return Innerlist.Remove(item);
    }

		#endregion

		#region IEnumerable<TEntity> Members

		/// <summary>
		/// Gets an enumerator for the list.
		/// </summary>
		/// <returns>Returns an enumerator for the list.</returns>
		IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
		{
			DoLoad();
			return Innerlist.GetEnumerator();
		}

		#endregion

		#region IEnumerable Members

		/// <summary>
		/// Gets an enumerator for the list.
		/// </summary>
		/// <returns>Returns an enumerator for the list.</returns>
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			DoLoad();
			return Innerlist.GetEnumerator();
		}

		#endregion

    #region INotifyCollectionChanged Members

		/// <summary>
		/// INotifyCollectionChanged.NotifyCollectionChangedEventHandler event.
		/// </summary>
    public event NotifyCollectionChangedEventHandler CollectionChanged;

    #endregion
  }
}
